/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor.expression;

import org.functor.Algorithms;
import org.functor.UnaryFunction;
import org.functor.UnaryPredicate;
import org.functor.UnaryProcedure;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Collections;

public interface PredicateExpression extends Expression {
	public Or or(PredicateExpression or);

	public And and(PredicateExpression and);

	public PredicateExpression not();

	/** Is this expression always true? Only "true" for instances of True. */
	public boolean isAlwaysTrue();

	/** Is this expression always false? Only "true" for instances of False. */
	public boolean isAlwaysFalse();


	public static class Utils {

		/**
		 * takes an expression and recursively traverses applying it to remove the Ors. The result is a set of
		 * expressions consisting of only Ands which if Or'd together would be equivalent to the original
		 * expression
		 */
		public static Collection<PredicateExpression> removeOrs(PredicateExpression p) {
			if (p==null)
				return Collections.emptyList();

			List<PredicateExpression> predicates = new ArrayList<PredicateExpression>();

			// the the product of removeOrs from each child of And must be And'ed with each of
			// the existing predicates
			if (p instanceof And) {
				for (PredicateExpression child : ((And) p).getPredicates()) {
					List<PredicateExpression> old_predicates = new ArrayList<PredicateExpression>(predicates);
					predicates.clear();
					Collection<PredicateExpression> childPredicates = removeOrs(child);

					if (old_predicates.size() == 0)
						predicates.addAll(childPredicates);
					else
						for (PredicateExpression predicate : old_predicates)
							for (PredicateExpression childPredicate : childPredicates)
								predicates.add(new AndImpl(predicate, childPredicate));
				}
			}
			// each product of removeOrs on the children of an Or is returned
			else if (p instanceof Or) {
				for (PredicateExpression child : ((Or) p).getPredicates())
					for (PredicateExpression predicate : removeOrs(child))
						predicates.add(predicate);
			}
			// apply de morgan's laws to a not'ed expression, if the result is just a notted predicate, return that
			// however if it was simplified into a junction-based statement, apply removeOrs to the junction
			// and return the result
			else if (p instanceof Not) {
				PredicateExpression expr = deMorganify(((Not) p).getPredicate());
				if (expr instanceof Not)
					predicates.add(expr);
				else
					predicates.addAll(removeOrs(expr));
			}
			// if it is a simple predicate, return the predicate
			else {
				predicates.add(p);
			}

			return predicates;
		}

		/**
		 * negates an expression and recursively applies de Morgan's laws to return a simple solution:<br/>
		 * <ol>
		 * <li>nots return their predicates</li>
		 * <li>Ands become Ors with their predicates negated</li>
		 * <li>Ors become And with their predicates negated</li>
		 * <li>Predicates are negated</li>
		 * </ol>
		 */
		public static PredicateExpression deMorganify(PredicateExpression expression) {
			if (expression == null)
				throw new IllegalArgumentException("expression must not be null");

			if (expression instanceof And)
				return new OrImpl(Algorithms.apply(((And) expression).getPredicates(),
						new UnaryFunction<PredicateExpression, PredicateExpression>() {
							public PredicateExpression evaluate(PredicateExpression predicate) {
								return deMorganify(predicate);
							}
						}));
			else if (expression instanceof Or)
				return new AndImpl(Algorithms.apply(((Or) expression).getPredicates(),
						new UnaryFunction<PredicateExpression, PredicateExpression>() {
							public PredicateExpression evaluate(PredicateExpression predicate) {
								return deMorganify(predicate);
							}
						}));
			else
				return expression.not();
		}

		/** traverses a boolean org.functor expression and returns all BooleanFunctors  that pass the test predicate */
		public static List<PredicateExpression> getPredicates(final UnaryPredicate<PredicateExpression> test, final PredicateExpression expression) {
			final List<PredicateExpression> predicates = new ArrayList<PredicateExpression>();
			ProcedureVisitor.visit(expression, new UnaryProcedure<Expression>() {
				public void run(Expression e) {
					if (e instanceof PredicateExpression) {
						PredicateExpression pred = (PredicateExpression) e;
						if (test.test(pred))
							predicates.add(pred);
					}
				}
			});

			return predicates;
		}
	}



}
